﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Text;

namespace Cobalt.Framework.Graphics
{

	public enum TextPlacement
	{
		/// <summary>
		/// Text is aligned to the top or left edge of the clipping box.
		/// </summary>
		Near,

		/// <summary>
		/// Text is centered in the clipping box.
		/// </summary>
		Center,

		/// <summary>
		/// Text is aligned to the right or bottom edge of the clipping box.
		/// </summary>
		Far,

		/// <summary>
		/// Text is justified within the clipping box.
		/// </summary>
		Justified,
	}

	[Flags]
	public enum TextClippingMode
	{
		/// <summary>
		/// Don't clip text.
		/// </summary>
		None = 0,

		/// <summary>
		/// Clip only to the width of the clipping box.
		/// </summary>
		Horizontal = 1,

		/// <summary>
		/// Clip only to the height of the clipping box.
		/// </summary>
		Vertical = 2,

		/// <summary>
		/// Clip to both the width and height of the clipping box.
		/// </summary>
		HorizontalAndVertical = Horizontal | Vertical,
	}

	public sealed class FormattingStack
	{
		private struct FormatState
		{
			public Color Color;
			public Vector2 GlyphScale;
		}

		public FormattingStack()
		{
			defaultFormat.Color = Color.White;
			defaultFormat.GlyphScale = Vector2.One;
		}

		private FormatState defaultFormat;

		private FormatState[] states = new FormatState[16];
		private int stackTop = 0;

		private void Push( ref FormatState state )
		{
			if( stackTop == states.Length )
			{
				var newStates = new FormatState[states.Length + 16];
				Array.Copy( states, newStates, stackTop );
				states = newStates;
			}

			states[stackTop++] = state;
		}

		internal void Assign( FormattingStack other )
		{
			defaultFormat = other.defaultFormat;

			if( other.stackTop > states.Length )
				states = new FormatState[other.states.Length];

			Array.Copy( other.states, states, other.stackTop );
			stackTop = other.stackTop;
		}

		/// <summary>
		/// The default formatting color, returned if no
		/// color value has been pushed.
		/// </summary>
		public Color DefaultColor
		{
			get { return defaultFormat.Color; }
			set { defaultFormat.Color = value; }
		}

		/// <summary>
		/// The default glyph scale value, returned if
		/// none has been pushed.
		/// </summary>
		public Vector2 DefaultGlyphScale
		{
			get { return defaultFormat.GlyphScale; }
			set { defaultFormat.GlyphScale = value; }
		}

		private FormatState Top { get { return stackTop > 0 ? states[stackTop - 1] : defaultFormat; } }

		internal void Clear()
		{
			stackTop = 0;
		}

		/// <summary>
		/// Pop the topmost change from the formatting stack.
		/// </summary>
		public void Pop()
		{
			if( stackTop > 0 )
				stackTop--;
		}

		/// <summary>
		/// The current formatting color.
		/// </summary>
		public Color Color
		{
			get { return Top.Color; }
		}

		/// <summary>
		/// The current glyph scale.
		/// </summary>
		public Vector2 GlyphScale
		{
			get { return Top.GlyphScale; }
		}

		/// <summary>
		/// Push a new <see cref="Color"/> value.
		/// </summary>
		/// <param name="newColor">The new value.</param>
		/// <remarks>
		/// The new value will overwrite the old.
		/// </remarks>
		public void PushColor( Color newColor )
		{
			FormatState state = Top;
			state.Color = newColor;
			Push( ref state );
		}

		/// <summary>
		/// Push a new <see cref="GlyphScale"/> value.
		/// </summary>
		/// <param name="newScale">The new value.</param>
		/// <remarks>
		/// The new value will overwrite the old.
		/// </remarks>
		public void PushGlyphScale( Vector2 newScale )
		{
			FormatState state = Top;
			state.GlyphScale = newScale;
			Push( ref state );
		}
	}

	public interface ITextFormattingHandler
	{
		/// <summary>
		/// Checks if the text at a given position is a formatting sequence,
		/// pushes the formatting info onto the formatting stack, and returns
		/// the index of the next character.
		/// </summary>
		/// <param name="text">The text source to check.</param>
		/// <param name="position">The position to check at.</param>
		/// <param name="stack">The formatting stack to push values onto.</param>
		/// <returns>
		/// The index of the next character after the formatting sequence,
		/// or <paramref name="position"/> if there was no formatting sequence at that point.
		/// </returns>
		int HandleFormattingSequence( TextSource text, int position, FormattingStack stack );
	}

	public sealed class TextLayout
	{

		/// <summary>
		/// Information about a laid-out line of text.
		/// </summary>
		public sealed class Line
		{
			internal TextSource text;
			/// <summary>
			/// Gets the starting index of this line's text,
			/// relative to the overall block of text to lay out.
			/// </summary>
			public TextSource Text { get { return text; } }

			internal int glyphCount;
			/// <summary>
			/// Gets the number of printabe characters in this line.
			/// </summary>
			public int GlyphCount { get { return glyphCount; } }

			internal Rectangle bounds;
			/// <summary>
			/// Gets this line's bounding rectangle.
			/// </summary>
			public Rectangle Bounds { get { return bounds; } }

			/// <summary>
			/// This is a width value to add at each space in order
			/// to justify the line (if so required).
			/// </summary>
			internal float xJustify;

			internal void Reset()
			{
				text = TextSource.Empty;
				glyphCount = 0;
				bounds = Rectangle.Empty;
				xJustify = 0;
			}
		}

		/// <summary>
		/// A collection of <see cref="Line"/>s.
		/// </summary>
		public sealed class LineCollection : IList<Line>
		{
			private Line[] lines;
			private int count;

			internal LineCollection( int capacity )
			{
				lines = new Line[capacity];
				for( int i = 0; i < lines.Length; i++ )
					lines[i] = new Line();
			}

			internal void Reset()
			{
				count = 0;
			}

			internal Line NewLine()
			{
				if( count == lines.Length )
				{
					var newLines = new Line[lines.Length + 4];
					
					Array.Copy( lines, newLines, count );
					for( int i = count; i < newLines.Length; i++ )
						newLines[i] = new Line();

					lines = newLines;
				}

				var ret = lines[count++];
				
				ret.Reset();

				return ret;
			}

			#region IList<Line> Members

			int IList<Line>.IndexOf( Line item )
			{
				return Array.IndexOf( lines, lines, 0, count );
			}

			void IList<Line>.Insert( int index, Line item )
			{
				throw new NotSupportedException();
			}

			void IList<Line>.RemoveAt( int index )
			{
				throw new NotSupportedException();
			}

			Line IList<Line>.this[int index]
			{
				get
				{
					if( index < 0 || index >= count )
						throw new ArgumentNullException();

					return lines[index];
				}
				set
				{
					throw new NotSupportedException();
				}
			}

			/// <summary>
			/// Gets the line at the given index.
			/// </summary>
			/// <param name="index">The index of the line to get.</param>
			/// <returns>The line at <paramref name="index"/>.</returns>
			public Line this[int index]
			{
				get
				{
					if( index < 0 || index >= count )
						throw new ArgumentOutOfRangeException();

					return lines[index];
				}
			}

			#endregion

			#region ICollection<Line> Members

			void ICollection<Line>.Add( Line item )
			{
				throw new NotSupportedException();
			}

			void ICollection<Line>.Clear()
			{
				throw new NotSupportedException();
			}

			bool ICollection<Line>.Contains( Line item )
			{
				return Array.IndexOf( lines, lines, 0, count ) != -1;
			}

			void ICollection<Line>.CopyTo( Line[] array, int arrayIndex )
			{
				Array.Copy( lines, 0, array, arrayIndex, count );
			}

			/// <summary>
			/// Gets the number of lines in the collection.
			/// </summary>
			public int Count
			{
				get { return count; }
			}

			bool ICollection<Line>.IsReadOnly
			{
				get { return true; }
			}

			bool ICollection<Line>.Remove( Line item )
			{
				throw new NotSupportedException();
			}

			#endregion

			#region IEnumerable<Line> Members

			/// <summary>
			/// Enumerates the lines in the collection.
			/// </summary>
			public struct Enumerator : IEnumerator<Line>
			{
				private LineCollection owner;
				
				private int index;
				private Line current;

				internal Enumerator( LineCollection owner )
				{
					this.owner = owner;
					this.index = 0;
					this.current = null;
				}

				#region IEnumerator<Line> Members

				/// <summary>
				/// The currently enumerated line.
				/// </summary>
				public Line Current
				{
					get { return current; }
				}

				#endregion

				#region IDisposable Members

				/// <summary>
				/// Releases all resources used by the enumerator.
				/// </summary>
				public void Dispose()
				{
				}

				#endregion

				#region IEnumerator Members

				object System.Collections.IEnumerator.Current
				{
					get { return current; }
				}

				/// <summary>
				/// Advances the enumerator to the next element.
				/// </summary>
				/// <returns>true if there is another element, false otherwise.</returns>
				public bool MoveNext()
				{
					if( index < owner.count )
					{
						current = owner.lines[index++];
						return true;
					}
					else
					{
						current = null;
						return false;
					}
				}

				void System.Collections.IEnumerator.Reset()
				{
					index = 0;
					current = null;
				}

				#endregion
			}

			/// <summary>
			/// Gets an enumerator over the collection.
			/// </summary>
			/// <returns>An enumerator over the line collection.</returns>
			public Enumerator GetEnumerator()
			{
				return new Enumerator( this );
			}

			IEnumerator<Line> IEnumerable<Line>.GetEnumerator()
			{
				return GetEnumerator();								
			}

			#endregion

			#region IEnumerable Members

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}

			#endregion
		}

		public enum GlyphType
		{
			/// <summary>
			/// The glyph is a space character.
			/// </summary>
			Space,
			/// <summary>
			/// The glyph is the carret character.
			/// </summary>
			Carret,
			/// <summary>
			/// The glyph is a regular glyph.
			/// </summary>
			Glyph,
		}

		public struct GlyphLayout
		{
			/// <summary>
			/// The type of the current glyph.
			/// </summary>
			public GlyphType Type;

			/// <summary>
			/// The sheet that holds the glyph's image data.
			/// </summary>
			public Texture2D Sheet;

			/// <summary>
			/// The sheet rectangle containing the glyph's image data.
			/// </summary>
			public Rectangle SourceRect;

			/// <summary>
			/// The glyph's render origin.
			/// </summary>
			public Vector2 Origin;
			/// <summary>
			/// The glyph's render size.
			/// </summary>
			public Vector2 Size;

			/// <summary>
			/// The amount of scaling between <see cref="SourceRect"/> and <see cref="Size"/>.
			/// </summary>
			public Vector2 Scale;

			/// <summary>
			/// The glyph's text color (based on formatting).
			/// </summary>
			public Color Color;
		}

		#region Text parameters

		private Font font;
		/// <summary>
		/// Gets or sets the current font.
		/// </summary>
		/// <remarks>
		/// Setting the font will discard any in-progress layout.
		/// </remarks>
		public Font Font
		{
			get { return font; }
			set
			{
				font = value;
				Reset();
			}
		}

		private ITextFormattingHandler formattingHandler;
		/// <summary>
		/// Gets or sets the current formatting handler.
		/// </summary>
		/// <remarks>
		/// Setting the font will discard any in-progress layout.
		/// </remarks>
		public ITextFormattingHandler FormattingHandler
		{
			get { return formattingHandler; }
			set
			{
				formattingHandler = value;
				Reset();
			}
		}

		private float textSize = 12;
		/// <summary>
		/// Gets or sets the text size in points.
		/// </summary>
		/// <remarks>
		/// Setting the font will discard any in-progress layout.
		/// </remarks>
		public float TextSize
		{
			get { return textSize; }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException();

				textSize = value;
			}
		}

		private TextPlacement horizontalPlacement = TextPlacement.Near;
		/// <summary>
		/// Gets or sets the horizontal text placement.
		/// </summary>
		/// <remarks>
		/// Setting this value will discard any in-progress layout.
		/// </remarks>
		public TextPlacement HorizontalPlacement
		{
			get { return horizontalPlacement; }
			set
			{
				horizontalPlacement = value;
				Reset();
			}
		}

		private TextPlacement verticalPlacement = TextPlacement.Near;
		/// <summary>
		/// Gets or sets the vertical text placement.
		/// </summary>
		/// <remarks>
		/// Setting this value will discard any in-progress layout.
		/// </remarks>
		public TextPlacement VerticalPlacement
		{
			get { return verticalPlacement; }
			set
			{
				verticalPlacement = value;
				Reset();
			}
		}

		private TextClippingMode clippingMode = TextClippingMode.HorizontalAndVertical;
		/// <summary>
		/// Gets or sets the text clipping mode.
		/// </summary>
		/// <remarks>
		/// Setting this value will discard any in-progress layout.
		/// </remarks>
		public TextClippingMode ClippingMode
		{
			get { return clippingMode; }
			set
			{
				clippingMode = value;
				Reset();
			}
		}

		private bool wordWrap = true;
		/// <summary>
		/// Enables or disables word wrapping.
		/// </summary>
		/// <remarks>
		/// Setting this value will discard any in-progress layout.
		/// </remarks>
		public bool WordWrap
		{
			get { return wordWrap; }
			set
			{
				wordWrap = value;
				Reset();
			}
		}

		private FormattingStack formatStack = new FormattingStack();
		private FormattingStack layoutStack = new FormattingStack();

		/// <summary>
		/// Gets or sets the default text color.
		/// </summary>
		/// <remarks>
		/// Setting the default color will discard any in-progress layout.
		/// </remarks>
		public Color DefaultColor
		{
			get { return formatStack.DefaultColor; }
			set
			{
				formatStack.DefaultColor = value;
				Reset();
			}
		}

		/// <summary>
		/// Gets or sets the default glyph scale.
		/// </summary>
		/// /// <remarks>
		/// Setting the default glyph scale will discard any in-progress layout.
		/// </remarks>
		public Vector2 DefaultGlyphScale
		{
			get { return formatStack.DefaultGlyphScale; }
			set
			{
				formatStack.DefaultGlyphScale = value;
				Reset();
			}
		}
		
		#endregion

		private LineCollection lines;
		public LineCollection Lines { get { return lines; } }

		private Line dummyLine = new Line();

		public TextLayout()
			: this( 16 )
		{
		}

		public TextLayout( int lineCapactiy )
		{
			lines = new LineCollection( lineCapactiy );
		}

		private int DoLayout( TextSource text, Vector2 layoutSize, int maxLines, int maxCharsPerLine, bool useDummyLine )
		{
			//validate the params for a layout

			if( font == null )
				throw new InvalidOperationException( "No font is set." );

			//prepare to do a new layout

			layoutStack.Assign( formatStack );

			bool xClip = (clippingMode & TextClippingMode.Horizontal) != TextClippingMode.None;
			bool yClip = (clippingMode & TextClippingMode.Vertical) != TextClippingMode.None;

			var basePoint = font.GetPoint( textSize );
			float baseScale = textSize / basePoint.PointSize;

			int sIndex = 0;

			int numLines;
			for( numLines = 0; numLines < maxLines; numLines++ )
			{
				int numSpaces = 0;
				int widthAtLastSpace = 0;
				int lastSpaceIndex = 0;

				float prevKernC = 0;
				bool firstCharInLine = true;

				var line = useDummyLine ? dummyLine : lines.NewLine();

				int lineStartIndex = sIndex;
				line.bounds.Height = (int)(basePoint.LineHeight * baseScale + 0.5F);
				line.bounds.Y = line.bounds.Height * numLines;

				while( sIndex < text.Count )
				{
					if( formattingHandler != null )
					{
						//consume formatting sequences
						for( int nextPos; sIndex < text.Count; sIndex = nextPos )
						{
							nextPos = formattingHandler.HandleFormattingSequence( text, sIndex, layoutStack );
							if( nextPos == sIndex )
								break;
						}
					}

					char ch = text[sIndex++];

					if( ch == '\n' )
						break;

					int gIdx = font.GetCharacterIndex( ch );
					if( gIdx < 0 )
						gIdx = font.DefaultGlyphIndex;

					var glyph = basePoint.Glyphs[gIdx];

					if( char.IsWhiteSpace( ch ) )
					{
						lastSpaceIndex = sIndex - 1;
						widthAtLastSpace = line.bounds.Width;
						numSpaces++;
					}

					float glyphWidth;
					if( firstCharInLine )
					{
						glyphWidth = Math.Max( glyph.KernA, 0 ) + glyph.KernB;
						firstCharInLine = false;
					}
					else
					{
						glyphWidth = prevKernC + basePoint.Spacing + glyph.KernA + glyph.KernB;
					}

					glyphWidth *= baseScale * layoutStack.GlyphScale.X;
					prevKernC = glyph.KernC;

					if( xClip && line.bounds.Width + glyphWidth > layoutSize.X )
					{
						if( wordWrap && numSpaces > 0 )
						{
							sIndex = lastSpaceIndex;
							line.bounds.Width = widthAtLastSpace;

							line.xJustify = numSpaces; //fixed up later
						}

						if( line.glyphCount == 0 )
						{
							line.bounds.Width = (int)(glyphWidth + 0.5F);
							line.glyphCount = 1;
						}

						break;
					}

					line.bounds.Width += (int)(glyphWidth + 0.5F); //may not want to round this right here...
					line.glyphCount++;

					if( line.glyphCount > maxCharsPerLine )
						break;
				}

				if( yClip && line.bounds.Bottom > layoutSize.Y )
					//that's as many lines as we can have...
					break;

				switch( horizontalPlacement )
				{
				case TextPlacement.Near:
					line.xJustify = 0;
					break;

				case TextPlacement.Center:
					line.bounds.X = (int)((layoutSize.X - line.bounds.Width) * 0.5F);
					line.xJustify = 0;
					break;

				case TextPlacement.Far:
					line.bounds.X = (int)(layoutSize.X - line.bounds.Width);
					line.xJustify = 0;
					break;

				case TextPlacement.Justified:
					if( line.xJustify > 0 )
						line.xJustify = (layoutSize.X - line.bounds.Width) / line.xJustify;
					break;
				}

				if( sIndex > 0 && text[sIndex - 1] != '\n' )
				{
					//skip any spaces between wrapped lines
					while( sIndex < text.Count )
					{
						if( formattingHandler != null )
						{
							//consume formatting sequences
							for( int nextPos; sIndex < text.Count; sIndex = nextPos )
							{
								nextPos = formattingHandler.HandleFormattingSequence( text, sIndex, layoutStack );
								if( nextPos == sIndex )
									break;
							}
						}

						if( !char.IsWhiteSpace( text[sIndex] ) )
							break;

						sIndex++;
					}
				}

				line.text = text.Substring( useDummyLine ? 0 : lineStartIndex, sIndex - lineStartIndex );

				if( sIndex == text.Count )
				{
					numLines++;
					break;
				}
			}

			if( !useDummyLine && numLines > 0 )
			{
				Line lastLine = lines[lines.Count - 1];

				float yScale = 1;
				float yBias = -(basePoint.LineHeight - basePoint.BaseLine) * baseScale;

				switch( verticalPlacement )
				{
				case TextPlacement.Near:
					break;

				case TextPlacement.Center:
					yBias += (layoutSize.Y - lastLine.bounds.Bottom) / 2;
					break;

				case TextPlacement.Far:
					yBias += layoutSize.Y - lastLine.bounds.Bottom;
					break;

				case TextPlacement.Justified:
					if( lastLine.bounds.Y > 0 )
						yScale = (layoutSize.Y - lastLine.bounds.Height) / lastLine.bounds.Y;
					goto case TextPlacement.Center;
				}

				float delta = 0;
				for( int i = 0; i < numLines; i++ )
				{
					float newY = lines[i].bounds.Y * yScale + yBias + delta;
					lines[i].bounds.Y = (int)(newY + 0.5F);

					delta = lines[i].bounds.Y - newY;
				}
			}

			return numLines;
		}

		/*
		public int CountLines( TextSource text, Vector2 layoutSize, int maxLines, int maxCharsPerLine )
		{
		}

		public int CountLines( TextSource text, Vector2 layoutSize, int maxLines )
		{
			return CountLines( text, layoutSize, maxLines, int.MaxValue );
		}

		public int CountLines( TextSource text, Vector2 layoutSize )
		{
			return CountLines( text, layoutSize, int.MaxValue, int.MaxValue );
		}
		*/

		public int LayoutLines( TextSource text, Point layoutOrigin, Vector2 layoutSize, int maxLines, int maxCharsPerLine )
		{
			int numLines = DoLayout( text, layoutSize, maxLines, maxCharsPerLine, false );
			
			for( int i = 0; i < numLines; i++ )
			{
				Line l = lines[lines.Count - i - 1];
				
				l.bounds.X += layoutOrigin.X;
				l.bounds.Y += layoutOrigin.Y;
			}

			return numLines;
		}

		public int LayoutLines( TextSource text, Point layoutOrigin, Vector2 layoutSize, int maxLines )
		{
			return LayoutLines( text, layoutOrigin, layoutSize, maxLines, int.MaxValue );
		}

		public int LayoutLines( TextSource text, Point layoutOrigin, Vector2 layoutSize )
		{
			return LayoutLines( text, layoutOrigin, layoutSize, int.MaxValue, int.MaxValue );
		}

		private struct GlyphLayoutState
		{
			public int LineIndex;
			public int CharIndex;
			
			public float XPosition;
			public float PrevKernC;

			public Vector2 Offset;
			public int CursorPos;

			public Font.FontPoint BasePoint;
			public float BaseScale;
		}

		private GlyphLayoutState gls;

		/// <summary>
		/// Begin laying out the glyphs generated by
		/// the last call to <see cref="LayoutLines"/>.
		/// </summary>
		/// <param name="offset">An offset to add to each glyph's position.</param>
		/// <param name="cursorPosition">The glyph index at which to inject a cursor glyph.</param>
		public void BeginGlyphLayout( Vector2 offset, int cursorPosition )
		{
			if( font == null )
				throw new InvalidOperationException( "No font is set." );

			layoutStack.Assign( formatStack );

			gls.LineIndex = -1;
			gls.CharIndex = -1;

			gls.Offset = offset;
			gls.CursorPos = cursorPosition;

			gls.BasePoint = font.GetPoint( textSize );
			gls.BaseScale = textSize / gls.BasePoint.PointSize;
		}

		/// <summary>
		/// Begin laying out the glyphs generated by
		/// the last call to <see cref="LayoutLines"/>.
		/// </summary>
		/// <param name="cursorPosition">The glyph index at which to inject a cursor glyph.</param>
		public void BeginGlyphLayout( int cursorPosition )
		{
			BeginGlyphLayout( Vector2.Zero, cursorPosition );
		}

		/// <summary>
		/// Begin laying out the glyphs generated by
		/// the last call to <see cref="LayoutLines"/>.
		/// </summary>
		/// <param name="offset">An offset to add to each glyph's position.</param>
		public void BeginGlyphLayout( Vector2 offset )
		{
			BeginGlyphLayout( offset, -1 );
		}

		/// <summary>
		/// Begin laying out the glyphs generated by
		/// the last call to <see cref="LayoutLines"/>.
		/// </summary>
		public void BeginGlyphLayout()
		{
			BeginGlyphLayout( Vector2.Zero, -1 );
		}

		/// <summary>
		/// Scan a given piece of text for formatting sequences,
		/// pushing each sequence onto the current formatting stack.
		/// </summary>
		/// <param name="priorText">The text to scan.</param>
		public void ScanForFormattingSquenences( TextSource priorText )
		{
			if( font == null )
				throw new InvalidOperationException( "No font is set." );

			if( formattingHandler == null )
				return;

			for( int i = 0; i < priorText.Count; i++ )
			{
				int nextCh = formattingHandler.HandleFormattingSequence( priorText, i, formatStack );
				if( nextCh != i )
					i = nextCh - 1;
			}
		}

		/// <summary>
		/// Compute the next glyph in the sequence.
		/// </summary>
		/// <param name="glyph">The computed glyph.</param>
		/// <returns>
		/// True if a glyph was computed, false if the
		/// end of the sequence had been reached.
		/// </returns>
		public bool LayoutNextGlyph( out GlyphLayout ret )
		{
			if( gls.BasePoint == null )
				throw new ArgumentNullException( "BeginGlyphLayout not called before LayoutNextGlyph." );

			ret = new GlyphLayout();
			ret.Scale = Vector2.One;

		begin:


			Line line;
			bool lineStart;

			if( gls.CharIndex == -1 )
			{
				if( gls.LineIndex == lines.Count - 1 )
					return false;

				gls.LineIndex++;

				line = lines[gls.LineIndex];
				gls.XPosition = 0;
				gls.CharIndex = 0;

				gls.PrevKernC = 0;

				lineStart = true;
			}
			else
			{
				line = lines[gls.LineIndex];
				lineStart = false;
			}

			if( gls.CursorPos == 0 )
			{
				ret.Type = GlyphType.Carret;

				float hh = gls.BasePoint.LineHeight * gls.BaseScale * layoutStack.GlyphScale.Y;

				ret.Origin.X = gls.Offset.X + line.bounds.X + gls.XPosition - 1;
				ret.Origin.Y = gls.Offset.Y + line.bounds.Y + hh * 0.25F;
				ret.Size.X = 2;
				ret.Size.Y = hh * 0.75F;

				ret.Color = layoutStack.Color;

				gls.CursorPos = -1;

				return true;
			}

			if( formattingHandler != null )
			{
				//consume formatting sequences
				for( int nextPos; gls.CharIndex < line.text.Count; gls.CharIndex = nextPos )
				{
					nextPos = formattingHandler.HandleFormattingSequence( line.text, gls.CharIndex, layoutStack );
					if( nextPos == gls.CharIndex )
						break;
				}
			}

			if( gls.CharIndex == line.text.Count )
			{
				gls.CharIndex = -1;
				goto begin;
			}

			if( gls.CursorPos > 0 )
				gls.CursorPos--;

			char ch = line.text[gls.CharIndex++];

			int gIdx = font.GetCharacterIndex( ch );
			if( gIdx < 0 )
				gIdx = font.DefaultGlyphIndex;

			var glyph = gls.BasePoint.Glyphs[gIdx];

			var letterScale = layoutStack.GlyphScale;
			var baseScale = letterScale * gls.BaseScale;

			if( lineStart )
			{
				gls.XPosition += Math.Max( glyph.KernA, 0 ) * baseScale.X;
			}
			else
			{
				gls.XPosition += (gls.PrevKernC + glyph.KernA + gls.BasePoint.Spacing) * baseScale.X;
			}

			float xPos = (int)(gls.XPosition + 0.5F);

			ret.Color = layoutStack.Color;

			if( char.IsWhiteSpace( ch ) )
			{
				ret.Type = GlyphType.Space;

				ret.Origin.X = gls.Offset.X + line.bounds.X + xPos;
				ret.Origin.Y = gls.Offset.Y + line.bounds.Y;
				ret.Size.X = 0;
				ret.Size.Y = gls.BasePoint.LineHeight * baseScale.Y;

				gls.XPosition += line.xJustify;
			}
			else
			{
				ret.Type = GlyphType.Glyph;

				ret.Sheet = glyph.Sheet;
				ret.SourceRect = glyph.SourceRectangle;

				ret.Origin.X = gls.Offset.X + line.bounds.X + xPos + glyph.DrawOffset.X * baseScale.X;
				ret.Origin.Y = gls.Offset.Y + line.bounds.Y + glyph.DrawOffset.Y * baseScale.Y;
				ret.Size.X = glyph.DrawSize.X * baseScale.X;
				ret.Size.Y = glyph.DrawSize.Y * baseScale.Y;

				ret.Scale = baseScale;

				if( letterScale.X != 1 )
					ret.Origin.X += glyph.SourceRectangle.Width * baseScale.X * (1 - letterScale.X) * 0.5F;

				if( letterScale.Y != 1 )
					ret.Origin.Y += glyph.SourceRectangle.Height * baseScale.Y * (1 - letterScale.Y) * 0.5F;
			}

			gls.XPosition += glyph.KernB * baseScale.X;
			gls.PrevKernC = glyph.KernC;

			return true;
		}

		/// <summary>
		/// Resets the current layout state.
		/// </summary>
		public void Reset()
		{
			formatStack.Clear();
			lines.Reset();

			gls.BasePoint = null;
		}

		public void Draw( SpriteBatch spriteBatch )
		{
			GlyphLayout glyph;
			while( LayoutNextGlyph( out glyph ) )
			{
				switch( glyph.Type )
				{
				case GlyphType.Glyph:
					spriteBatch.Draw( glyph.Sheet, glyph.Origin, glyph.SourceRect,
						glyph.Color, 0, Vector2.Zero, glyph.Scale, SpriteEffects.None, 0 );
					break;
				}
			}
		}

		public void Draw( SpriteBatch spriteBatch, Color overrideColor )
		{
			GlyphLayout glyph;
			while( LayoutNextGlyph( out glyph ) )
			{
				switch( glyph.Type )
				{
				case GlyphType.Glyph:
					spriteBatch.Draw( glyph.Sheet, glyph.Origin, glyph.SourceRect,
						overrideColor, 0, Vector2.Zero, glyph.Scale, SpriteEffects.None, 0 );
					break;
				}
			}
		}


		public Rectangle GetLayoutBounds()
		{
			if( lines.Count == 0 )
				return new Rectangle();

			Rectangle ret = lines[0].bounds;

			for( int i = 1; i < lines.Count; i++ )
			{
				Rectangle bd = lines[i].bounds;

				ret.X = Math.Min( ret.X, bd.X );
				ret.Width = Math.Max( ret.Right, bd.Right ) - ret.X;

				ret.Y = Math.Min( ret.Y, bd.Y );
				ret.Height = Math.Max( ret.Bottom, bd.Bottom ) - ret.Y;
			}

			return ret;			
		}
	}

}